package tsproxy

import (
	"bytes"
	"encoding/json"
	"errors"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/influxdata/influxql"
)

type MoQueryParams struct {
	// 按类型查询
	ByTypes         []string
	// 按 IP 地址 查询
	ByAddresses     []string
	// 按 设备名 查询
	ByNames         []string
	// 按 设备Tag 查询
	ByTags          []string
	// 按管理域名查询
	ByDomainName    string
	// 查询未指定管理域名的管理对象
	ByDefaultDomain bool
}

func (params *MoQueryParams) ToQuery() url.Values {
	var u = url.Values{}
	if len(params.ByTypes) > 0 {
		u["by_type"] = params.ByTypes
	}
	if len(params.ByAddresses) > 0 {
		u["by_address"] = params.ByAddresses
	}
	if len(params.ByNames) > 0 {
		u["by_name"] = params.ByNames
	}
	if len(params.ByTags) > 0 {
		u["by_tag"] = params.ByTags
	}
	if params.ByDefaultDomain {
		u["by_default_domain"] = []string{"true"}
	}
	if params.ByDomainName != "" {
		u["by_domain_name"] = []string{params.ByDomainName}
	}
	return u
}

func (m *MoQueryParams) IsZero(excludeName string) bool {
	if len(m.ByTypes) > 0 {
		if excludeName != "mo_type" {
			return false
		}
	}

	if len(m.ByAddresses) > 0 {
		if excludeName != "mo_address" {
			return false
		}
	}

	if len(m.ByNames) > 0 {
		if excludeName != "mo_name" {
			return false
		}
	}

	if len(m.ByTags) > 0 {
		if excludeName != "mo_tag" {
			return false
		}
	}

	return true
}

type TypeInfo struct {
	ID    int64  `json:"id,omitempty"`
	Name  string `json:"name,omitempty"`
	Label string `json:"label,omitempty"`
}

func (st *TypeInfo) UnmarshalJSON(b []byte) error {
	return st.UnmarshalText(b)
}

func (st *TypeInfo) UnmarshalText(b []byte) error {
	if bytes.HasPrefix(b, []byte("{")) {
		var t struct {
			ID    int64  `json:"id,omitempty"`
			Name  string `json:"name,omitempty"`
			Label string `json:"label,omitempty"`
		}
		err := json.Unmarshal(b, &t)
		if err != nil {
			return err
		}

		st.ID = t.ID
		st.Name = t.Name
		st.Label = t.Label
		return nil
	}
	b = bytes.Trim(b, "\"")
	ss := bytes.SplitN(b, []byte(":"), 3)
	switch len(ss) {
	case 1:
		st.Name = string(b)
	case 2:
		i64, err := strconv.ParseInt(string(ss[0]), 10, 64)
		if err != nil {
			return err
		}
		st.ID = i64
		st.Name = string(ss[1])
	case 3:
		i64, err := strconv.ParseInt(string(ss[0]), 10, 64)
		if err != nil {
			return err
		}
		st.ID = i64
		st.Name = string(ss[1])
		st.Label = string(ss[2])
	}
	return nil
}

type ManagedObject struct {
	// 管理对象唯一 ID
	ID int64 `json:"id,omitempty"`

	// 管理对象主类型
	Type TypeInfo `json:"type"`

	// 管理对象的自定义名称
	CustomName string `json:"custom_name,omitempty"`

	// 管理对象的显示名称
	DisplayNames []DisplayName `json:"display_names,omitempty"`

	// 创建时间
	CreatedAt time.Time `json:"created_at,omitempty"`

	// 最后一次更新时间
	UpdatedAt time.Time `json:"updated_at,omitempty"`

	Fields struct {
		Address string `json:"address,omitempty"`
		Name    string `json:"name,omitempty"`
	} `json:"fields,omitempty"`
}

type DisplayName struct {
	Format string `json:"format,omitempty"`
	Name   string `json:"name"`
}

type Error struct {
	Code    int    `json:"code,omitempty"`
	Message string `json:"message,omitempty"`
}

func (err *Error) Error() string {
	return err.Message
}

// Result 查询的结果
type Result struct {
	Data     []ManagedObject `json:"data,omitempty"`
	Messages []string        `json:"messages,omitempty"`
	Errors   []*Error        `json:"errors,omitempty"`
}

func GetManagedObjects(urlstr string, params *MoQueryParams) ([]ManagedObject, error) {
	var result Result
	if strings.Contains(urlstr, "?") {
		urlstr = urlstr + "&" + params.ToQuery().Encode()
	} else {
		urlstr = urlstr + "?" + params.ToQuery().Encode()
	}
	resp, err := http.Get(urlstr)
	if err != nil {
		return nil, err
	}
	if resp.Body != nil {
		defer resp.Body.Close()
	}

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(resp.Status)
	}
	decoder := json.NewDecoder(resp.Body)
	decoder.UseNumber()
	err = decoder.Decode(&result)
	if err != nil {
		return nil, err
	}

	if len(result.Errors) > 0 {
		return nil, result.Errors[0]
	}
	return result.Data, nil
}

func RewriteMoQueryExpr(urlstr string) func(*MoQueryParams) (influxql.Expr, error) {
	return func(params *MoQueryParams) (influxql.Expr, error) {
		moList, err := GetManagedObjects(urlstr, params)
		if err != nil {
			return nil, err
		}

		var ss = make([]string, len(moList))
		for idx := range moList {
			ss[idx] = strconv.FormatInt(moList[idx].ID, 10)
		}
		return toInExpr("managed_object_id", ss), nil
	}
}
